#include <vector>
#include <string>
#include <limits.h>

#include "Image.H"
#include "PixelIterator.H"
#include "Util.H"
#include "IO.H"


#ifndef funduspreprocessingH
#define funduspreprocessingH


// Return minimum and maximum of the input image
template<class T>
void getMinMaxAvrImage(const pip::Image<T> &in,float &min, float &max, float &avr)
{
        min = max = (float)in(0);

        int numPixels = in.dim(2)*in.dim(1)*in.dim(0);
        avr = (T)0;
        for (int iz=0;iz<in.dim(2);++iz)
        for (int iy=0;iy<in.dim(1);++iy)
        for (int ix=0;ix<in.dim(0);++ix) {
                T f = in(ix,iy,iz);
                if (f<min) min = f;
                if (f>max) max = f;
                avr += (float)f;
        }
        if (numPixels > 0)
        avr /= numPixels;
		//std::cout << " Min: " << float(T(min)) << " \tMax: " << float(T(max)) << " \tAvr: "<< float(T(avr)) << std::endl;
}

template<class Tout>
class PreProcessFundus {
 private:
  // Extract one color channel from a RGB image.
  // input:  RGB Image imIn
  // output: Image inOut
  // upon successful completion, the function returns 0, else it gives 1.
  int extractColorChannel(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<unsigned char>& imOut);
  char color; // color channel to use ('R' = red, 'G' = green, 'B' = blue)

  // Median filter an image
  // input:  Image imInOut
  // output: Image imInOut
  // Upon successful completion, the function returns 0, else it gives 1.
  int medianFilter(pip::Image<unsigned char>& imInOut);
  int iMedianWidth; // width of the median kernel (square kernel is assumed).

 public:
  PreProcessFundus(): color('R'), iMedianWidth(15){};
  // Run the algorithm
  // input:  RGB Image imIn
  // output: Image imOut
  // Upon successful completion, the function returns 0, else it gives 1.
  int run(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<Tout>& imOut);

  // Set the color channel to use ('R' = red, 'G' = green, 'B' = blue).
  // Return the actual color channel set.
  char setColorChannel(char _color){
    color = _color;
    if(color != 'R' && color != 'G' && color != 'B')
      color = 'R';
    return(color);
  }

  // Set the width of the median to use. Return the actual median width.
  int setMedianWidth(int _iMedianWidth){
    iMedianWidth = _iMedianWidth;
    return(iMedianWidth);
  }
};

template<class Tout>
int PreProcessFundus<Tout>::medianFilter(pip::Image<unsigned char> &imInOut)
{
        pip::Image<unsigned char> imTemp(imInOut);

        int iKernelCenter = iMedianWidth/2;
        int iWidth  = imInOut.dim(0);
        int iHeight = imInOut.dim(1);

        int iX, iY;

        int iNrPixelsLEQMedian = 1 + (iMedianWidth * iMedianWidth)/2;
		std::vector<int> hist(256);

        for (int y=0; y<iHeight; ++y)
        for (int x=0; x<iWidth; ++x) {

                // Reset histogram values
                for (int i=0; i<256; ++i)
                hist[i] = 0;

                // Assume non-addressable pixels have value 0
                for (int m=0; m<iMedianWidth; ++m)
                for (int n=0; n<iMedianWidth; ++n) {
                        iX = x+(m-iKernelCenter);
                        iY = y+(n-iKernelCenter);
                        if ( iX >= 0 && iX < iWidth && iY >= 0 && iY < iHeight)
                        hist[imTemp(iX,iY)]++;
                        else
                        hist[0]++;
                }

                // Find the median in the cumulative histogram
                int iLTMedian = 0;
                int iMedian = -1;
                while (iLTMedian < iNrPixelsLEQMedian) {
                        iMedian++;
                        iLTMedian += hist[iMedian];
                }

                imInOut(x,y) = iMedian;
        }
        return 0;
}

template<class Tout>
int PreProcessFundus<Tout>::run(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<Tout>& imOut){
  int errors = 0;
  float min,max,avr;
  
  pip::Image<unsigned char> singlecolor(imIn.dimension());
  errors += extractColorChannel(imIn,singlecolor);        // extract the selected color channel from imIn
  pip::Image<unsigned char> imMedian(singlecolor);
  errors += medianFilter(imMedian);                       // median filter the gray-scale image

  getMinMaxAvrImage(singlecolor,min,max,avr);

  for(int z=0; z< imIn.dim(2); ++z){
	  for(int y=0; y< imIn.dim(1); ++y){
		  for(int x=0; x< imIn.dim(0); ++x){
			  imOut(x,y,z)=float(singlecolor(x,y,z))-float(imMedian(x,y,z));
		  }
	  }
  }

  getMinMaxAvrImage(imOut,min,max,avr);

  return(errors);
}


template<class Tout>
int PreProcessFundus<Tout>::extractColorChannel(const pip::Image<pip::Color<unsigned char> >& imIn, pip::Image<unsigned char>& imOut){
	int errors(0);

	//lets use a function pointer
	unsigned char (pip::Color<unsigned char>::*rgb)(void);  
	if(color=='R')
		rgb=&pip::Color<unsigned char>::red;
	else if(color=='G')
		rgb=&pip::Color<unsigned char>::green;
	else if(color=='B')
		rgb=&pip::Color<unsigned char>::blue;

	pip::Dimension row = 0;

	pip::PixelIterator<pip::Color<unsigned char> > i; // iterator for input image
	pip::PixelIterator<unsigned char> j; // iterator for output image

	do {
			// initialize to the beginning of the row
      		i = imIn.begin(row);
			j = imOut.begin(row);
			
			while(i!= imIn.end(row)) {
				*j = ((*i).*rgb)(); // does not work with '->' ...
				++i;
				++j;
			}
	}
	while (imIn.nextRow(row));
	return(errors);  //the function must return a int...
}





// here to functions, which i thought might be helpful ... they just convert images... 
// they are not tested and do not handle problems...
template <class T>
pip::Image<unsigned char> convertAllToUnsignedChar(const pip::Image<T> &input)
{
	//some image to char image	
    pip::Image<unsigned char> output;
    output.resize(input.dimension());

	if(input.dim(0)){
	  T min, max, avr;
	  getMinMaxAvrImage(input,min,max,avr);
	  if (min == max) {
		output.fill(128);
		return output;
	  }
	  
	  float scale( float(std::numeric_limits<unsigned char>::max()) / (max - min));
	   for (int z = 0; z < input.dim(2); ++z) {
		for (int y = 0; y < input.dim(1); ++y) {
		  for (int x = 0; x < input.dim(0); ++x) {
			output(x, y, z) = (unsigned char)(scale * (input(x, y, z) - min));
		  }
		}
	  }
	}
  return output;
}

// some char image to any other image
template <class T>
void convertUnsignedCharToAll(const pip::Image<unsigned char> &input,pip::Image<T> &output)
{
  output.resize(input.dimension());

  for (int z = 0; z < input.dim(2); ++z) {
    for (int y = 0; y < input.dim(1); ++y) {
      for (int x = 0; x < input.dim(0); ++x) {
        output(x, y, z) = (T)input(x, y, z);
      }
    }
  }
}


#endif
